package com.logicbus.backend.websocket.scanner;

import com.alogic.load.Scanner;
import com.anysoft.util.*;
import com.logicbus.backend.websocket.WSEndpointInfo;
import com.logicbus.backend.websocket.WSEndpointScanner;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;

import javax.websocket.server.ServerEndpoint;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 扫描package，通过Annotation来发现
 *
 * @version 1.6.14.11 [20210524 duanyy] <br>
 * - 修复jar搜索问题 <br>
 */
public class FromPackage extends Scanner.Abstract<WSEndpointInfo> implements WSEndpointScanner,Scanner.Listener<WSEndpointInfo> {
    protected List<WSEndpointInfo> objList = new ArrayList<WSEndpointInfo>();
    protected boolean hasScan = false;
    protected String packages = null;
    protected String delimiter = ";";

    @Override
    public void configure(Properties p){
        super.configure(p);
        packages = PropertiesConstants.getString(p,"package","",true);
        delimiter = PropertiesConstants.getString(p,"delimiter",delimiter,true);
    }

    @Override
    public void scan(Listener<WSEndpointInfo> listener) {
        if (listener != null) {
            Object cookies = listener.begin(this.getId());
            String[] list = packages.split(delimiter);
            for (String item:list){
                LOG.info(String.format("Scan endpoint in package %s",item));
                scan(cookies,listener,item);
            }
            listener.end(cookies,this.getId());
        }
    }

    protected static String dotToSplash(String name){
        return name.replaceAll("\\.","/");
    }

    protected static String trimExtension(String name){
        int pos = name.indexOf(".");
        if (-1 != pos){
            return name.substring(0,pos);
        }
        return name;
    }

    private Class<?> loadClass(String aClass) {
        Class<?> found = null;
        if (StringUtils.isNotEmpty(aClass)){
            ClassLoader cl = Settings.getClassLoader();
            try {
                found = cl.loadClass(aClass);
            }catch (Exception ex){
                LOG.error("Can not load class:" + aClass);
            }
        }
        return found;
    }

    protected void scan(Object cookies,Listener<WSEndpointInfo> listener,String packageName){
        String home = dotToSplash(packageName);
        URL url = Settings.getClassLoader().getResource(home);
        if (url == null){
            LOG.warn("Package is not found:" + packageName);
            return ;
        }
        if(url.toString().startsWith("file:")){
            File file;
            try {
                file = new File(url.toURI());
            } catch (URISyntaxException e) {
                LOG.error("Can not scan home:" + home,e);
                LOG.error(ExceptionUtils.getStackTrace(e));
                return;
            }
            LOG.info(String.format("Scan endpoint in %s",url.toString()));
            scanFileSystem(home,file,packageName,cookies,listener);
        }
        else{
            if (url.toString().startsWith("jar:")){
                JarFile jfile;
                try {
                    String jarUrl = url.toString();
                    jarUrl = jarUrl.substring(9, jarUrl.indexOf('!'));
                    jfile = new JarFile(jarUrl);
                } catch (IOException e) {
                    LOG.error("Can not scan home:" + home,e);
                    LOG.error(ExceptionUtils.getStackTrace(e));
                    return;
                }
                LOG.info(String.format("Scan endpoint in %s",url.toString()));
                scanJar(home,jfile,packageName,cookies,listener);
            }
        }
    }

    protected void scanFileSystem(String home, File file, String packageName, Object cookies, Listener<WSEndpointInfo> listener) {
        File[] files = file.listFiles();
        for (File item:files){
            String filename = item.getName();
            if (filename.endsWith(".class")){
                String className = packageName + "." + trimExtension(filename);
                scanClass(className,cookies,listener);
            }
        }
    }

    protected void scanJar(String home, JarFile jarFile, String packageName, Object cookies, Listener<WSEndpointInfo> listener) {
        Enumeration<JarEntry> files = jarFile.entries();
        while (files.hasMoreElements()) {
            JarEntry entry = files.nextElement();
            String name = entry.getName();
            if (name.endsWith(".class")) {
                int end = name.lastIndexOf('/');
                if (end > 0) {
                    String path = name.substring(0, end);
                    if (path.equals(home)) {
                        String filename = name.substring(end + 1);
                        String className = packageName + "." + trimExtension(filename);
                        scanClass(className, cookies, listener);
                    }
                }
            }
        }
    }

    protected void scanClass(String classname,Object cookies,Listener<WSEndpointInfo> listener){
        Class<?> clazz = loadClass(classname);
        if (clazz != null){
            ServerEndpoint anno = (ServerEndpoint)clazz.getAnnotation(ServerEndpoint.class);
            if (anno != null) {
                WSEndpointInfo info = new WSEndpointInfo(clazz,anno.value());
                if (listener != null) {
                    listener.found(cookies, info);
                }
            }
        }
    }

    @Override
    public List<WSEndpointInfo> getList() {
        if (!hasScan){
            scan(this);
            hasScan = true;
        }
        return objList;
    }

    @Override
    public Object begin(String scannerId) {
        return this;
    }

    @Override
    public void found(Object cookie, WSEndpointInfo data) {
        objList.add(data);
    }

    @Override
    public void end(Object cookie, String scannerId) {

    }
}
